using System;
using System.Collections.Generic;
using System.Reflection;
using System.Xml;

namespace XmlObjects.XmlWriter
{
	public class ElementToPropertyMap
	{
//		private PropertyInfo propertyInfo;
//		private PropertyAttribute propertyAttribute;

		public bool HasSubTypes
		{
			get { return subTypeMap != null && subTypeMap.Count > 0; }
		}

		public Type DefaultSubType
		{
			get { return defaultSubType; }
			set { defaultSubType = value; }
		}

		public string FindByString
		{
			get { return findByString; }
			set { findByString = value; }
		}

		public XmlNodeType NodeType_
		{
			get { return NodeType; }
			set { NodeType = value; }
		}


		private Type defaultSubType = null;
		private string findByString = "#name";

		private Dictionary<string, Type> subTypeMap = new Dictionary<string, Type>();

		public Dictionary<string, Type> SubTypeMap
		{
			get { return subTypeMap; }
			set { subTypeMap = value; }
		}

		private XmlNodeType NodeType = XmlNodeType.Element;
		private string _nodeName;


		public string SelectString
		{
			get { return selectString; }
			set { selectString = value; }
		}

		private string selectString;

		public XmlNode AppendNodeTo(XmlNode node, Type type)
		{
			string[] pathElements = selectString.Split('/');
			XmlNode nodeToAppendTo = node;
			for (int i = 0; i < -1 + pathElements.Length; i++)
			{
				XmlNode tempNode = nodeToAppendTo.SelectSingleNode(pathElements[i]);
				if (tempNode == null)
					tempNode = nodeToAppendTo.AppendChild(nodeToAppendTo.OwnerDocument.CreateElement(pathElements[i]));
				nodeToAppendTo = tempNode;
			}

			XmlDocument document = GetDocFrom(node);
			if (NodeType == XmlNodeType.Attribute)
			{
				return nodeToAppendTo.Attributes.Append(document.CreateAttribute(GetNodeName(type)));
			}
			else
			{
				return nodeToAppendTo.AppendChild(document.CreateElement(GetNodeName(type)));
			}
		}

		private string GetNodeName(Type type)
		{
			if (!_nodeName.Contains("*"))
				return _nodeName;
			else
			{
				foreach (KeyValuePair<string, Type> keyValuePair in subTypeMap)
				{
					if (type == keyValuePair.Value)
						return keyValuePair.Key;
				}
				throw new XmlObjectsConfigurationException("Miscondivu");
			}
		}

		private XmlDocument GetDocFrom(XmlNode node)
		{
			if (node.OwnerDocument == null) return (XmlDocument) node;
			else return node.OwnerDocument;
		}

		public ElementToPropertyMap(Type type)
		{
			if (Attribute.IsDefined(type, typeof (SerializeableAttribute)))
			{
				SerializeableAttribute attribute =
					(SerializeableAttribute) Attribute.GetCustomAttribute(type, typeof (SerializeableAttribute));

				ReadPropertyAttribute(attribute);
			}
			Validate();
		}

		public ElementToPropertyMap(PropertyInfo propertyInfo)
		{
			foreach (Attribute attribute in Attribute.GetCustomAttributes(propertyInfo))
			{
				if (attribute is AttribAttribute)
				{
					NodeType = XmlNodeType.Attribute;
					ReadPropertyAttribute(attribute);
				}
				else if (attribute is PropertyAttribute)
				{
					ReadPropertyAttribute(attribute);
				}
				else if (attribute is SubTypeAttribute)
				{
					SubTypeAttribute subTypeAttribute = (SubTypeAttribute) attribute;
					subTypeMap.Add(subTypeAttribute.Id, subTypeAttribute.Type);
					if (subTypeAttribute.IsDefault)
						defaultSubType = subTypeAttribute.Type;
				}
			}
			Validate();
		}

		private void Validate()
		{
			if (!HasSubTypes && selectString.Contains("*"))
				throw new XmlObjectsConfigurationException("Can't specify a wildcard without specifying a subtype.");
			else if (selectString.Contains("*") && findByString != "#name")
				throw new XmlObjectsConfigurationException(
					"Can't specify a wildcard when FindByString is other than the default of #name.");
		}

		private void ReadPropertyAttribute(Attribute attribute)
		{
			PropertyAttribute propertyAttribute = (PropertyAttribute) attribute;
			_nodeName = propertyAttribute.NodeName;
			selectString = propertyAttribute.SelectString;
			findByString = propertyAttribute.FindSubTypeBy;
		}
	}

	internal class XmlObjectsConfigurationException : Exception
	{
		public XmlObjectsConfigurationException(string s) : base(s)
		{
		}
	}
}